import * as d3 from "d3";
import dayjs from "dayjs";

class Heatmap {
  constructor(dom, options) {
    this._dom = dom;
    this._options = Object.assign({}, Heatmap.defaultOptions, options);
  }

  update(data, options) {
    if (options) {
      this._options = Object.assign({}, this._options, options);
      this._xAxisDom = this._options.xAxisDom;
    }

    this._data = data;
    this._draw();
  }

  clear() {
    d3.select(this._dom).selectAll("*").remove();
    if (this._xAxisDom) d3.select(this._xAxisDom).selectAll("*").remove();
  }

  destroy() {
    d3.select(this._dom).selectAll("*").remove();
    if (this._xAxisDom) d3.select(this._xAxisDom).selectAll("*").remove();
  }

  _intervals(startDate, endDate, interval) {
    const days = [];
    let current = dayjs(startDate);

    while (current <= dayjs(endDate)) {
      days.push(current.format(this._options.dateFormat));
      current =
        interval === "hour"
          ? current.add(1, "hour")
          : interval === "day"
          ? current.add(1, "day")
          : interval === "week"
          ? current.add(1, "week")
          : interval === "month"
          ? current.add(1, "month")
          : current.add(1, "year");
    }

    return days;
  }

  _draw() {
    this.clear();

    const _ = this;
    const max = Math.max.apply(
      null,
      this._data.map((d) => d.value)
    );
    const min = Math.min.apply(
      null,
      this._data.map((d) => d.value)
    );
    // 唯一值处理
    // const xDim = Array.from(new Set(data.map((d) => d.x_dim)));
    // const yDim = Array.from(new Set(data.map((d) => d.y_dim)));
    const xDim = this._intervals(this._options.start, this._options.end, "day");
    const yDim = Array.from(new Set(this._data.map((d) => d.y_dim)));

    const data = xDim
      .map((xd) => {
        const valueData = this._data.filter((_) => _.x_dim === xd);
        if (valueData && valueData.length > 0) {
          // return valueData;

          return yDim.map((yd) => {
            const vd = valueData.find((vd) => vd.y_dim == yd);
            return {
              x_dim: xd,
              y_dim: yd,
              value: vd?.value,
              color: vd?.color,
            };
          });
        } else {
          return yDim.map((yd) => ({
            x_dim: xd,
            y_dim: yd,
            value: null,
          }));
        }
      })
      .flat();

    const gridSize = this._options.size;
    const tickSpan = this._options.dateFormat.length / 2 + 1;
    const margin = {
      left: this._options.left
        ? this._options.left + tickSpan * 5
        : tickSpan * 5,
      right: tickSpan * 5,
      top: 0,
      bottom: this._xAxisDom ? 0 : this._options.fontSize + 4,
    };

    function xTickFormat(d, i) {
      // this is text node 未渲染
      // const bbox = this.getBBox(); // 获取文本的边界框
      // if (i > 0 && this.getComputedTextLength() > bbox.width) {
      //     // text.style("display", "none"); // 如果不适合，则隐藏文本
      //     return null;
      // } else {
      //     // text.style("display", null); // 否则显示文本
      //     return d;
      // }

      if (i % tickSpan) {
        return null;
      } else {
        return d;
      }

      // if (this.parentNode.nextSibling) {

      // }
    }

    const width = gridSize * xDim.length;
    const height = gridSize * yDim.length;

    // 准备svg
    const svg = d3
      .select(this._dom)
      .append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", `translate(${margin.left}, ${margin.top})`);

    // Build X scales and axis:
    const x = d3
      .scaleBand()
      .range([0, gridSize * xDim.length])
      .domain(xDim)
      .padding(0.05);

    if (this._xAxisDom) {
      const xAxisSvg = d3
        .select(this._xAxisDom)
        .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", this._options.fontSize + 5)
        .append("g")
        .attr("transform", `translate(${margin.left}, 0)`);
      xAxisSvg
        .append("g")
        .style("font-size", this._options.fontSize)
        .attr("transform", `translate(0, 0)`)
        .call(d3.axisBottom(x).tickSize(0).tickFormat(xTickFormat))
        .select(".domain")
        .remove();
    } else {
      svg
        .append("g")
        .style("font-size", this._options.fontSize)
        .attr("transform", `translate(0, ${height})`)
        .call(d3.axisBottom(x).tickSize(0).tickFormat(xTickFormat))
        .select(".domain")
        .remove();
    }

    // Build Y scales and axis:
    const y = d3.scaleBand().range([height, 0]).domain(yDim).padding(0.05);
    // svg.append("g")
    //     .style("font-size", this._options.fontSize)
    //     .call(d3.axisLeft(y).tickSize(0))
    //     .select(".domain")
    //     .remove();

    // Build color scale
    const scaleColor = d3
      .scaleLinear()
      .domain([0, max / 2, max])
      .range(this._options.colors);

    let tooltip;
    if (this._options.tooltip) {
      // create a tooltip
      tooltip = d3
        .select(this._dom)
        .select(".heatmap-chart-tooltip")
        // .append("div")
        .style("opacity", 0);
      // .attr("class", "heatmap-tooltip");
      // .style("background-color", "white")
      // .style("border", "solid")
      // .style("border-width", "2px")
      // .style("border-radius", "5px")
      // .style("padding", "5px");

      // Three function that change the tooltip when user hover / move / leave a cell
      const mouseover = function (event, d) {
        // tooltip.style("opacity", 1);
        d3.select(this).style("stroke", "black").style("opacity", 1);
        _._options.mouseover && _._options.mouseover(event, d);
      };
      const mousemove = function (event, d) {
        // tooltip
        //     .html("The exact value of<br>this cell is: " + d.value)
        //     .style("left", event.x / 2 + "px")
        //     .style("top", event.y / 2 + "px");

        // tooltip
        //     .html("The exact value of<br>this cell is: " + d.value)
        //     .style(
        //         "transform",
        //         `translate(${event.x / 2 + "px"}, ${
        //             event.y / 2 + "px"
        //         })`
        //     );
        _._options.mouseover && _._options.mousemove(event, d);
      };
      const mouseleave = function (event, d) {
        // tooltip.style("opacity", 0);
        d3.select(this).style("stroke", "none").style("opacity", 0.8);
        _._options.mouseover && _._options.mouseleave(event, d);
      };

      // add the squares
      svg
        .selectAll()
        .data(data, function (d) {
          return d.x_dim + ":" + d["y_dim"];
        })
        .join("rect")
        .attr("x", function (d) {
          return x(d.x_dim);
        })
        .attr("y", function (d) {
          return y(d.y_dim);
        })
        .attr("rx", this._options.radius)
        .attr("ry", this._options.radius)
        .attr("width", x.bandwidth())
        .attr("height", y.bandwidth())
        .style("fill", function (d) {
          return d.value !== null && d.value !== undefined
            ? d.color
              ? d.color
              : scaleColor(d.value)
            : _._options.nullValueColor;
        })
        .style("stroke-width", 1)
        .style("stroke", "none")
        .style("opacity", 0.8)
        .on("mouseover", mouseover)
        .on("mousemove", mousemove)
        .on("mouseleave", mouseleave);
    } else {
      // add the squares
      svg
        .selectAll()
        .data(data, function (d) {
          return d.x_dim + ":" + d.y_dim;
        })
        .join("rect")
        .attr("x", function (d) {
          return x(d.x_dim);
        })
        .attr("y", function (d) {
          return y(d.y_dim);
        })
        .attr("rx", this._options.radius)
        .attr("ry", this._options.radius)
        .attr("width", x.bandwidth())
        .attr("height", y.bandwidth())
        .style("fill", function (d) {
          return d.color || colors(d.value);
        })
        .style("stroke-width", 4)
        .style("stroke", "none")
        .style("opacity", 0.8);
    }

    this._options.drawEnd && this._options.drawEnd(yDim.reverse());
  }
}

Heatmap.defaultOptions = {
  height: 450,
  size: 15,
  radius: 4,
  fontSize: 14,
  start: new Date(),
  end: new Date().setMonth(new Date().getMonth() + 1),
  nullValueColor: "#CCCCCC",
  dateFormat: "YYYY-MM-DD", //YYYY-MM-DD, YYYY-MM, MM-DD
  margin: { top: 25, right: 25, bottom: 30, left: 30 },
  colors: ["#FFFFDD", "#3E9583", "#1F2D86"],
  tooltip: true,
  mouseover: () => {},
  mousemove: () => {},
  mouseleave: () => {},
};

export default Heatmap;
export { d3 };
